package abinly.harmony.permission.compiler;

import abinly.harmony.permission.PermissionHelper;
import com.squareup.javapoet.*;

import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import java.util.ArrayList;
import java.util.List;

public class CompilePermissionGroupGenerator implements IPermissionGroupGenerator {

    private static final String APPLY_PERMISSION_SUFFIX = "_PERMISSIONS";
    private static final String REQUEST_CODE_SUFFIX = "_REQUEST_CODE";

    private final PermissionGroupElement mPermissionGroupElement;
    private final TypeElement mTypeElement;

    public CompilePermissionGroupGenerator(TypeElement typeElement, PermissionGroupElement mPermissionGroupElement) {
        this.mTypeElement = typeElement;
        this.mPermissionGroupElement = mPermissionGroupElement;
    }

    @Override
    public String generatePermissionFieldName() {
        ExecutableElement element = mPermissionGroupElement.getNeedsSelfPermissionElement();

        if (element != null) {
            String methodName = element.getSimpleName().toString();
            return methodName.toUpperCase() + APPLY_PERMISSION_SUFFIX;
        }

        return APPLY_PERMISSION_SUFFIX;
    }

    @Override
    public String generateRequestCodeFieldName() {
        ExecutableElement element = mPermissionGroupElement.getNeedsSelfPermissionElement();

        if (element != null) {
            String methodName = element.getSimpleName().toString();
            return methodName.toUpperCase() + REQUEST_CODE_SUFFIX;
        }

        return REQUEST_CODE_SUFFIX;
    }

    @Override
    public CodeBlock generatePermissionResultCheckCodeBlock() {
        ExecutableElement element = mPermissionGroupElement.getNeedsSelfPermissionElement();

        CodeBlock.Builder builder = CodeBlock.builder();
        if (element != null) {
            String methodName = element.getSimpleName().toString();
            String permissionsFieldName = generatePermissionFieldName();
            String requestCodeFieldName = generateRequestCodeFieldName();

            builder.beginControlFlow("if ($L == requestCode)", requestCodeFieldName);
            builder.addStatement("$T target = TARGET_OBJECT_MAPS.remove(requestCode)", ClassName.get(mTypeElement));
            builder.beginControlFlow("if ($T.verifyPermissions(grantResults))", PermissionHelper.class);
            builder.addStatement("target.$L()", methodName);

            ExecutableElement onPermissionBlockedElement = mPermissionGroupElement.getOnPermissionBlockedElement();
            if (onPermissionBlockedElement != null) {
                builder.nextControlFlow("else if (!$T.canRequestPermission(target, $L))", PermissionHelper.class, permissionsFieldName);
                builder.addStatement("target.$L()", onPermissionBlockedElement.getSimpleName().toString());
            }
            builder.endControlFlow();
            builder.endControlFlow();
        }
        return builder.build();
    }

    @Override
    public FieldSpec generatePermissionFieldSpec() {
        ExecutableElement element = mPermissionGroupElement.getNeedsSelfPermissionElement();

        if (element != null) {
            String permissionsFieldName = generatePermissionFieldName();
            String[] permissions = mPermissionGroupElement.getNeedsPermissionArrays();
            FieldSpec.Builder builder = FieldSpec.builder(String[].class, permissionsFieldName);
            builder.addModifiers(Modifier.STATIC, Modifier.PRIVATE, Modifier.FINAL);
            builder.initializer(CodeBlockHelper.makeStringArrayCodeBlock(permissions));
            return builder.build();
        }

        return null;
    }

    @Override
    public FieldSpec generateRequestCodeFieldSpec() {
        ExecutableElement element = mPermissionGroupElement.getNeedsSelfPermissionElement();

        if (element != null) {
            String requestCodeFieldName = generateRequestCodeFieldName();
            FieldSpec.Builder builder = FieldSpec.builder(int.class, requestCodeFieldName);
            builder.addModifiers(Modifier.STATIC, Modifier.PRIVATE, Modifier.FINAL);
            builder.initializer("$L", mPermissionGroupElement.getRequestCode());
            return builder.build();
        }

        return null;
    }

    @Override
    public List<MethodSpec> generateRequestPermissionMethodSpec() {
        List<MethodSpec> methodSpecs = new ArrayList<>();

        ExecutableElement selfPermissionElement = mPermissionGroupElement.getNeedsSelfPermissionElement();
        if(selfPermissionElement != null){
            methodSpecs.add(generateRequestPermissionMethodSpec(selfPermissionElement, "verifySelfPermissions"));
        }

        ExecutableElement callingPermissionElement = mPermissionGroupElement.getNeedsCallingPermissionElement();
        if(callingPermissionElement != null){
            methodSpecs.add(generateRequestPermissionMethodSpec(callingPermissionElement, "verifyCallingPermissions"));
        }

        ExecutableElement callingOrSelfPermissionElement = mPermissionGroupElement.getNeedsCallingOrSelfPermissionElement();
        if(callingOrSelfPermissionElement != null){
            methodSpecs.add(generateRequestPermissionMethodSpec(callingOrSelfPermissionElement, "verifyCallingOrSelfPermissions"));
        }

        return methodSpecs;
    }

    private MethodSpec generateRequestPermissionMethodSpec(ExecutableElement element, String verifyMethod) {
        String methodName = element.getSimpleName().toString();
        String permissionsFieldName = generatePermissionFieldName();
        String requestCodeFieldName = generateRequestCodeFieldName();

        MethodSpec.Builder builder = MethodSpec.methodBuilder(methodName);
        builder.addModifiers(Modifier.PUBLIC, Modifier.STATIC);
        builder.addParameter(ParameterSpec.builder(ClassName.get(mTypeElement), "target").build());

        List<? extends VariableElement> variableElements = element.getParameters();
        for (VariableElement variableElement : variableElements) {
            builder.addParameter(ParameterSpec.get(variableElement));
        }

        builder.beginControlFlow("if (!$T.$L(target, $L))", PermissionHelper.class, verifyMethod, permissionsFieldName);
        builder.beginControlFlow("if ($T.canRequestPermission(target, $L))", PermissionHelper.class, permissionsFieldName);

        builder.beginControlFlow("if (!TARGET_OBJECT_MAPS.containsValue(target))");
        builder.addStatement("TARGET_OBJECT_MAPS.put($L, target)", requestCodeFieldName);
        builder.endControlFlow();
        builder.addStatement("target.requestPermissionsFromUser($L, $L)", permissionsFieldName, requestCodeFieldName);

        ExecutableElement onPermissionBlockedElement = mPermissionGroupElement.getOnPermissionBlockedElement();
        if (onPermissionBlockedElement != null) {
            builder.nextControlFlow("else");
            builder.addStatement("target.$L()", onPermissionBlockedElement.getSimpleName().toString());
        }
        builder.endControlFlow();

        builder.nextControlFlow("else");
        String methodPrefixStr = "target." + methodName;
        builder.addStatement(CodeBlockHelper.makeMethodParameterCodeBlock(methodPrefixStr, variableElements));
        builder.endControlFlow();

        return builder.build();
    }

}
